package com.zyplayer.data.screen.controller;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zyplayer.data.screen.framework.db.executor.ExecuteParam;
import com.zyplayer.data.screen.framework.db.executor.ExecuteResult;
import com.zyplayer.data.screen.framework.db.executor.ExecuteType;
import com.zyplayer.data.screen.framework.db.executor.SqlExecutor;
import com.zyplayer.data.screen.framework.exception.ConfirmException;
import com.zyplayer.data.screen.framework.json.DataResponse;
import com.zyplayer.data.screen.framework.json.ResponseJson;
import com.zyplayer.data.screen.repository.manage.entity.DbDatasource;
import com.zyplayer.data.screen.repository.manage.entity.Screen;
import com.zyplayer.data.screen.repository.manage.entity.ScreenComponent;
import com.zyplayer.data.screen.repository.manage.entity.ScreenComponentRelease;
import com.zyplayer.data.screen.service.manage.*;
import com.zyplayer.data.screen.utils.ThreadPoolUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 大屏数据控制器
 *
 * @author 暮光：城中城
 * @since 2019年12月13日
 */
@RestController
@RequestMapping("/screen/data")
public class ScreenDataController {
	private static Logger logger = LoggerFactory.getLogger(ScreenDataController.class);
	
	@Resource
	ScreenComponentService screenComponentService;
	@Resource
	ScreenComponentReleaseService screenComponentReleaseService;
	@Resource
	SqlExecutor sqlExecutor;
	@Resource
	PrivateKeyService privateKeyService;
	@Resource
	ScreenService screenService;
	@Resource
	DbDatasourceService dbDatasourceService;
	
	@PostMapping(value = "/develop")
	public ResponseJson list(String ids, String urlParam) {
		privateKeyService.validateSecureParam();
		QueryWrapper<ScreenComponent> wrapper = new QueryWrapper<>();
		wrapper.in("id", Arrays.asList(ids.split(",")));
		wrapper.select("id", "data_type", "datasource_id", "data_sql", "setting");
		Collection<ScreenComponent> screenComponents = screenComponentService.list(wrapper);
		Map<String, Object> resultMap = this.getResultByComponent(screenComponents, urlParam);
		return DataResponse.ok(resultMap);
	}
	
	@PostMapping(value = "/preview")
	public ResponseJson preview(ScreenComponent component) {
		privateKeyService.validateSecureParam();
		QueryWrapper<DbDatasource> wrapper = new QueryWrapper<>();
		wrapper.eq("yn", 1);
		wrapper.eq("id", component.getDatasourceId());
		DbDatasource dbDatasourceSel = dbDatasourceService.getOne(wrapper);
		if (dbDatasourceSel == null) {
			return DataResponse.warn("未找到该数据源");
		}
		ExecuteResult executeResult;
		try {
			String dataSql = this.getParserString(component.getDataSql(), Collections.emptyMap());
			ExecuteParam executeParam = new ExecuteParam();
			executeParam.setDatasourceId(component.getDatasourceId());
			executeParam.setExecuteId(this.getExecuteId(RandomUtil.randomLong()));
			executeParam.setExecuteType(ExecuteType.SELECT);
			executeParam.setSql(dataSql);
			executeParam.setMaxRows(100);
			executeResult = sqlExecutor.execute(executeParam);
		} catch (Exception e) {
			logger.error("执行sql错误：", e);
			executeResult = ExecuteResult.error(ExceptionUtils.getFullStackTrace(e), "");
		}
		return DataResponse.ok(executeResult);
	}
	
	/**
	 * 获取发布后的组件数据
	 *
	 * @param ids
	 * @return
	 */
	@PostMapping(value = "/released")
	public ResponseJson released(String ids, String password, String urlParam) {
		QueryWrapper<ScreenComponentRelease> wrapper = new QueryWrapper<>();
		wrapper.in("id", Arrays.asList(ids.split(",")));
		wrapper.select("id", "data_type", "datasource_id", "data_sql", "screen_id");
		Collection<ScreenComponentRelease> releaseList = screenComponentReleaseService.list(wrapper);
		if (CollectionUtils.isEmpty(releaseList)) {
			return DataResponse.ok();
		}
		// 数据校验
		Set<Long> screenIdList = releaseList.stream().map(ScreenComponentRelease::getScreenId).collect(Collectors.toSet());
		if (CollectionUtils.isEmpty(screenIdList)) {
			return DataResponse.error("组件未指定大屏");
		}
		if (screenIdList.size() > 1) {
			return DataResponse.error("只能查询一个大屏的数据");
		}
		QueryWrapper<Screen> screenWrapper = new QueryWrapper<>();
		screenWrapper.eq("screen_id", screenIdList.stream().findFirst().orElse(0L));
		Screen screen = screenService.getOne(screenWrapper);
		if (screen == null) {
			return DataResponse.error("未找到指定的大屏信息");
		}
		if (Objects.equals(screen.getViewType(), 1) && !Objects.equals(screen.getViewPassword(), password)) {
			return DataResponse.error("访问密码错误");
		}
		List<ScreenComponent> screenComponents = releaseList.stream().map(val -> {
			ScreenComponent component = new ScreenComponent();
			component.setId(val.getId());
			component.setDataType(val.getDataType());
			component.setDatasourceId(val.getDatasourceId());
			component.setDataSql(val.getDataSql());
			return component;
		}).collect(Collectors.toList());
		Map<String, Object> resultMap = this.getResultByComponent(screenComponents, urlParam);
		return DataResponse.ok(resultMap);
	}
	
	/**
	 * 获取组件查询的数据
	 *
	 * @param screenComponents 组件列表
	 * @return
	 */
	private Map<String, Object> getResultByComponent(Collection<ScreenComponent> screenComponents, String urlParam) {
		Map<String, Object> resultMap = new HashMap<>();
		if (CollectionUtils.isEmpty(screenComponents)) {
			return resultMap;
		}
		Map<String, String> urlParamMap = StringUtils.isNotBlank(urlParam) ? JSON.parseObject(urlParam, new TypeReference<Map<String, String>>() {
		}) : Collections.emptyMap();
		CountDownLatch countDownLatch = new CountDownLatch(screenComponents.size());
		Map<Long, Future<Object>> futureMap = new HashMap<>();
		screenComponents.forEach(component -> {
			Future<Object> future = ThreadPoolUtil.getThreadPool().submit(() -> {
				Object resultData = null;
				try {
					if (ComponentDataType.MYSQL.name().equalsIgnoreCase(component.getDataType())) {
						resultData = this.getDataForMysql(component, urlParamMap);
					} else if (ComponentDataType.API.name().equalsIgnoreCase(component.getDataType())) {
						resultData = this.getDataForApi(component, urlParamMap);
					}
				} catch (Exception e) {
					logger.error("提交任务异常", e);
				}
				countDownLatch.countDown();
				return resultData;
			});
			futureMap.put(component.getId(), future);
		});
		try {
			// 等待3秒钟
			countDownLatch.await(3, TimeUnit.SECONDS);
		} catch (Exception e) {
			logger.error("等待任务执行异常", e);
			throw new ConfirmException("查询出错，请重试");
		}
		for (Map.Entry<Long, Future<Object>> entry : futureMap.entrySet()) {
			try {
				// 已完成的组装数据，否则取消执行，等待100毫秒，防止countDown完成了但return还未完成
				Object componentData = entry.getValue().get(100, TimeUnit.MILLISECONDS);
				if (entry.getValue().isDone()) {
					if (componentData != null) {
						resultMap.put(this.getComponentName(entry.getKey()), componentData);
					}
				} else {
					sqlExecutor.cancel(this.getExecuteId(entry.getKey()));
					entry.getValue().cancel(true);
				}
			} catch (Exception e) {
				sqlExecutor.cancel(this.getExecuteId(entry.getKey()));
				logger.error("获取任务结果异常", e);
			}
		}
		return resultMap;
	}
	
	/**
	 * 获取MySQL类型的数据
	 *
	 * @param component 组件
	 * @return
	 */
	private List<Map<String, Object>> getDataForMysql(ScreenComponent component, Map<String, String> urlParamMap) {
		if (StringUtils.isBlank(component.getDataSql())) {
			return null;
		}
		String dataSql = this.getParserString(component.getDataSql(), urlParamMap);
		ExecuteParam executeParam = new ExecuteParam();
		executeParam.setDatasourceId(component.getDatasourceId());
		executeParam.setExecuteId(this.getExecuteId(component.getId()));
		executeParam.setExecuteType(ExecuteType.SELECT);
		executeParam.setSql(dataSql);
		executeParam.setMaxRows(1000);
		ExecuteResult executeResult = sqlExecutor.execute(executeParam);
		return executeResult.getResult();
	}
	
	/**
	 * 获取API类型的数据
	 *
	 * @param component 组件
	 * @return
	 */
	private List<?> getDataForApi(ScreenComponent component, Map<String, String> urlParamMap) {
		if (StringUtils.isBlank(component.getApiUrl())) {
			return null;
		}
		String apiUrl = this.getParserString(component.getApiUrl(), urlParamMap);
		String apiBodyStr = HttpRequest.get(apiUrl).execute().body();
		JSONArray resultJson = null;
		JSONObject bodyJson = JSON.parseObject(apiBodyStr);
		if (StringUtils.isNotBlank(component.getApiDataPlace())) {
			String[] split = component.getApiDataPlace().split("\\.");
			for (int i = 0; i < split.length; i++) {
				if (i == split.length - 1) {
					resultJson = bodyJson.getJSONArray(split[i]);
				} else {
					bodyJson = bodyJson.getJSONObject(split[i]);
				}
			}
		}
		return resultJson;
	}
	
	/**
	 * 处理预处理参数
	 *
	 * @return
	 */
	private String getParserString(String str, Map<String, String> urlParamMap) {
		GenericTokenParser parser = new GenericTokenParser("${", "}", content -> {
			Object o = urlParamMap.get(content);
			return (o == null) ? null : String.valueOf(o);
		});
		return parser.parse(str);
	}
	
	/**
	 * 获取前端所需的组件数据ID名
	 *
	 * @param componentId 组件ID
	 * @return
	 */
	private String getComponentName(Long componentId) {
		return "component_" + componentId;
	}
	
	/**
	 * 获取执行ID，用于sql层面的取消执行，否则一直等待sql执行会卡住当前线程
	 *
	 * @param componentId 组件ID
	 * @return
	 */
	private String getExecuteId(Long componentId) {
		return "sqlExecuteId_" + componentId;
	}
	
	/**
	 * 组件的数据类型
	 */
	public enum ComponentDataType {
		MYSQL, API,
	}
}

